require_relative "xml_model"

module XMLHelper
  class XMLBuilder
    def initialize(flat_xml)
      @flat_xml = flat_xml
    end

    def build_xml_document
      run_parser
    end

    private
    def run_parser
      tokens = run_lexer
      declaration = XMLModel::Declaration.new
      root = XMLModel::Element.new
      xml_doc = XMLModel::Document.new(declaration, root)

      decl_token0 = tokens.shift

      raise MissingDeclarationError if decl_token0.nil?
      raise MissingDeclarationError unless decl_token0[:type] == :declaration_begin

      decl_token1 = tokens.shift
      decl_token2 = tokens.shift
      decl_token3 = tokens.shift

      raise InvalidDeclarationError unless
        decl_token1[:type] == :attribute && decl_token2[:type] == :attribute && decl_token3[:type] == :declaration_end

      [decl_token1,decl_token2].each do |token|
        case token[:params][0]
        when "version"
          declaration.set_version token[:params][1]
        when "encoding"
          declaration.set_encoding token[:params][1]
        else
          raise InvalidDeclarationError
        end
      end

      root_token = tokens.shift

      raise NoRootTagError if root_token.nil?
      raise NoRootTagError unless [:tag_begin, :tag_begin_closing].include? root_token[:type]
      root.set_name(root_token[:params][0])

      level = 0
      stack = []
      stack.push(root.get_name)

      tag_unclosed = true

      for token in tokens
        params = token[:params]
        type = token[:type]

        if stack.empty?
          raise MultipleRootTagError unless [:tag_end, :tag_end_self_closing].include? type
        end

        if tag_unclosed
          raise MalformedTagError unless [:attribute, :tag_end_self_closing, :tag_end].include? type
        end

        cursor = xml_doc.get_cursor()

        case type
        when :attribute
          cursor.set_attribute(params[0],params[1])
        when :comment
          cursor.insert_child(XMLModel::Comment.new(params[0]) )
        when :tag_begin_closing
          if tag_unclosed
            raise MalformedTagError
          else
            tag_unclosed = true
          end
          if params[0] == stack.last
            stack.pop
          else
            raise UnbalancedTagsError
          end
          xml_doc.move_cursor_to_parent()
        when :tag_end_self_closing
          if tag_unclosed
            tag_unclosed = false
          else
            raise MalformedTagError
          end
          stack.pop

          xml_doc.move_cursor_to_parent()
        when :tag_begin
          if tag_unclosed
            raise MalformedTagError
          else
            tag_unclosed = true
          end
          stack.push(params[0])
          cursor.insert_child(XMLModel::Element.new(params[0]) )
          xml_doc.move_cursor_to_last_child()
        when :tag_end
          if tag_unclosed
            tag_unclosed = false
          else
            raise MalformedTagError
          end
        when :text
          cursor.insert_child(XMLModel::PlainText.new(params[0]) )
        when :declaration_begin, :declaration_end
          raise UnexpectedDeclarationError
        else
          raise UndefinedTokenTypeError
        end
      end

      xml_doc
    end

    def run_lexer
      characters = @flat_xml
      regexps = {
        comment: /\A<!--([^\/<>]+)-->/,
        declaration_begin: /\A<\?xml/,
        declaration_end: /\A\?>/,
        tag_begin_closing: /\A<\/([A-Za-z0-9_\-.,]+)/,
        tag_end_self_closing: /\A\/>/,
        tag_begin: /\A<([A-Za-z0-9_\-.,]+)/,
        tag_end: /\A>/,
        attribute: /\A([A-Za-z0-9_\-.,]+)=\"([^"]+)\"/,
        whitespace: /\A[ \r\n\s]/,
        text: /\A([ A-Za-z0-9_\-.,\t]+)/
      }

      pos = 0
      tokens = []

      while pos < characters.size
        mch = nil

        for regexp in regexps
          key, pattern = regexp
          mch = characters[pos..-1].match(pattern)
          if mch
            unless key == :whitespace
              tokens << {type: key, params:mch[1..-1]}
            end
            pos += mch.end(0)
            break
          end
        end

        if not mch
          raise XMLSyntaxError
          break
        end
      end
      tokens
    end

  end

  class XMLBuildError < StandardError ; end

  class MissingDeclarationError < XMLBuildError
    def initialize
      super("The declaration is missing.")
    end
  end

  class InvalidDeclarationError < XMLBuildError
    def initialize
      super("The declaration is invalid.")
    end
  end

  class NoRootTagError < XMLBuildError
    def initialize
      super("The root tag is missing.")
    end
  end

  class MalformedTagError < XMLBuildError
    def initialize
      super("The XML file has a malformed tag.")
    end
  end

  class UnbalancedTagsError < XMLBuildError
    def initialize
      super("The XML file has unbalanced tags.")
    end
  end

  class UnexpectedDeclarationError < XMLBuildError
    def initialize
      super("The XML file has an unexpected declaration in the XML body.")
    end
  end

  class UndefinedTokenTypeError < XMLBuildError
    def initialize
      super("The XML file has an undefined token type.")
    end
  end

  class MultipleRootTagError < XMLBuildError
    def initialize
      super("The XML file has multiple root tags.")
    end
  end

  class XMLSyntaxError < XMLBuildError
    def initialize
      super("The XML file syntax is corrupted.")
    end
  end

end